/***************************************************************************
 *   Copyright (C) 2007 by Ivan Vasić              *
 *   ivasic@gmail.com                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "mysqlannouncehandler.h"
#include "announcerequest.h"
#include "standardmessages.h"
#include "announceresponse.h"
#include "sha1hash.h"
#include "exceptions.h"

#include <QTcpSocket>

#include <QtSql>
#include <QtCore>		

MySQLAnnounceHandler::MySQLAnnounceHandler(QObject *parent)
		: AnnounceHandler(parent)
{}


MySQLAnnounceHandler::~MySQLAnnounceHandler()
{
}

void MySQLAnnounceHandler::handleRequest(Request* request)
{
	if(request->getType() != Request::ANNOUNCE)
		return; //throw exception or something
	
	QMutex m;
	QMutexLocker lock (&m);
	
	AnnounceRequest* announce = dynamic_cast<AnnounceRequest*>(request);
	
	if(!checkUser(announce))
		return; //throw ?
	
	//Na kraju odradi announce
	//PROCEDURE `spAnnounce`(IN infohash CHAR(20), IN peerID CHAR(20), IN in_port INT, IN in_uploaded BIGINT, IN in_downloaded BIGINT, IN in_left BIGINT, IN in_compact BOOL, IN in_event VARCHAR(10), IN in_numwant INT, IN in_ip INT, IN in_key VARCHAR(100), IN in_passkey CHAR(20))
	
	QSqlQuery query;
	query.prepare("CALL spAnnounce(?,?,?,?,?,?,?,?,?,?,?,?)");
	query.bindValue(0, announce->infohash().toString(), QSql::In);
	query.bindValue(1, announce->peerID(), QSql::In);
	query.bindValue(2, announce->port(), QSql::In);
	query.bindValue(3, announce->uploaded(), QSql::In);
	query.bindValue(4, announce->downloaded(), QSql::In);
	query.bindValue(5, announce->left(), QSql::In);
	query.bindValue(6, announce->compact(), QSql::In);
	query.bindValue(7, announce->announceEventToString(announce->event()), QSql::In);
	query.bindValue(8, announce->numwant(), QSql::In);
	query.bindValue(9, announce->ip().toIPv4Address(), QSql::In);
	query.bindValue(10, announce->key(), QSql::In);
	query.bindValue(11, announce->passkey(), QSql::In);
	
	query.exec();
	
	if(query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
	
	switch ( announce->event() )
	{		
		case EVENT_STOPPED:
		case EVENT_NOT_SET:
			return; //ne treba response, return
			
		case EVENT_COMPLETED:
		case EVENT_STARTED:
		case EVENT_EMPTY:
			break; //treba response, produzi dalje
	}
	
	//Count peers
	int complete = 0;
	int incomplete = 0;
	
	query.prepare("CALL spCountPeers(?,@complete,@incomplete)");
	query.bindValue(0, announce->infohash().toString(), QSql::In);

	query.exec();
	query.exec("SELECT @complete, @incomplete");
	query.next();
	
	if(query.lastError().type() != QSqlError::NoError || !query.isValid())
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
	
	complete = query.value(0).toInt();
	incomplete = query.value(1).toInt();
	
	query.prepare("SELECT Peer.PEER_ID as PEER_ID, Peer.IP AS IP, Peer.PORT AS PORT FROM Peer WHERE Peer.PEER_ID IN (SELECT DISTINCT PEER_ID FROM (SELECT Torrent_Peer.PEER_ID FROM Torrent_Peer WHERE Torrent_Peer.INFO_HASH = ?) AS PEERS)");
	
	query.bindValue(0, announce->infohash().toString(), QSql::In);
	query.exec();
	
	if(query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
	
	//Napravim Response
	///@TODO: Hardcoded interval. Pomeri ga u settings.
	AnnounceResponse n(30, complete, incomplete);
	
	while(query.next())
		n.addPeer(query.record().value("PEER_ID").toString(), QHostAddress(query.record().value("IP").toUInt()), query.record().value("PORT").toUInt());		

	announce->socket()->write(n.toString());	
}

bool MySQLAnnounceHandler::checkUser(const Request* request)
{
	const AnnounceRequest* announce = dynamic_cast<const AnnounceRequest*>(request);
	
	//Ako je peer registrovan sve je OK, vrati TRUE
	if(peerExists(announce->peerID()))
		return true;
	
	//Ali ako nije, treba proveriti da li postoji user/passkey
	QSqlQuery query;
	query.prepare("CALL spPasskeyExists(?,@v)");
	query.bindValue(0, announce->passkey(), QSql::In);
	query.exec();
	query.exec("SELECT @v");
	query.next();
	
	if(query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
	
	if(!query.value(0).toBool())
	{
		StandardMessages::sendFailure(tr("Passkey not valid. Please, re-download this torrent and try again."), announce->socket());
		return false; //throw error PASSKEY not valid!!! or not?
	}
	
	
	query.prepare("CALL spRegisterPeer(?,?,?,?,?,?)");
	query.bindValue(0, announce->peerID(), QSql::In);
	query.bindValue(1, announce->infohash().toString(), QSql::In);
	query.bindValue(2, announce->ip().toIPv4Address(), QSql::In);
	query.bindValue(3, announce->port(), QSql::In);
	query.bindValue(4, announce->key(), QSql::In);
	query.bindValue(5, announce->passkey(), QSql::In);
	
	query.exec();
	
	if(query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
	
	return true;
}

bool MySQLAnnounceHandler::peerExists(const QByteArray & peerID) const
{
	QSqlQuery query;
	query.prepare("CALL spPeerExists(?,@v)");
	query.bindValue(0, peerID, QSql::In);
	query.exec();
	query.exec("SELECT @v");
	query.next();
	
	if(query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Normal, __FILE__, __FUNCTION__, __LINE__);
	
	return query.value(0).toBool();
}

bool MySQLAnnounceHandler::isTorrentRegistered(const SHA1Hash& infohash) const
{
	QSqlQuery query;
	query.prepare("CALL spTorrentExists(?,@v)");
	query.bindValue(0, infohash.toString(), QSql::In);
	query.exec();
	query.exec("SELECT @v");
	query.next();
	
	if(query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Normal, __FILE__, __FUNCTION__, __LINE__);
	
	return query.value(0).toBool();
}
